
public class Percolation {

    //
    private int N;
    private WeightedQuickUnionUF qf;
    private boolean[][] open;
    private boolean[][] full;

    public static void main(String[] args) {
        //Test output for main method to see if things work properly
        Percolation p = new Percolation(100);
        PercolationVisualizer pV = new PercolationVisualizer(p);
        while(!p.percolates()) {
            while(true){
                int x = StdRandom.uniform(p.getN());
                int y = StdRandom.uniform(p.getN());
                if(!p.isOpen(x,y)){
                    p.open(x,y);
                    break;
                }
            }
            pV.draw();
        }
    }

    // create N-by-N grid, with all sites blocked
    public Percolation(int N) {
        this.N = N;
        this.qf = new WeightedQuickUnionUF(N * N);
        this.open = new boolean[N][N];
        this.full = new boolean[N][N];
    }

    //convert 2d coordinate to interger
    private int coordinateToInteger(int x, int y) {
        return x + y * this.N;
    }

    // open site (row x, col y) if it is not already
    public void open(int x, int y) {
        this.open[x][y] = true;
        //Right
        this.link(x, y, x+1, y);
        //Left
        this.link(x, y, x-1, y);
        //Top
        this.link(x, y, x, y+1);
        //Bottom
        this.link(x, y, x, y-1);
        //Make "flood fill" if site has got filled.
        if(this.isFull(x, y)){
            this.floodFill(x, y);
        }
    }

    //Link two sites if (x2,y2) is open.
    private void link(int x1, int y1, int x2, int y2){
        if(this.isOpen(x2,y2)){
            this.qf.union(this.coordinateToInteger(x1,y1),this.coordinateToInteger(x2,y2));
        }
        //Fill site if neighboor is full or site is at the top row
        if(this.isFull(x2,y2) || y1==this.N-1){
            this.fill(x1, y1);
        }
    }

    // Flood fill function
    public void floodFill(int ix, int iy){
        for(int x=0; x<this.N; x++){
            for(int y=0; y<this.N; y++){
                if(this.qf.connected(this.coordinateToInteger(ix, iy),this.coordinateToInteger(x, y))){
                    this.fill(x,y);
                }
            }
        }
    }

    // Fill all sites
    public void fill(int x, int y) {
        this.full[x][y] = true;        
    }

    // is site (row x, col y) open?
    public boolean isOpen(int x, int y) {
        if(x<this.N && x>=0 && y<this.N && y>=0){
            return this.open[x][y];
        }
        return false;
    }

    // is site (row x, col y) full?
    public boolean isFull(int x, int y) {
        if(x<this.N && x>=0 && y<this.N && y>=0){
            return this.full[x][y];
        }
        return false;
    }

    // does the system percolate?
    public boolean percolates() {
        for(int i=0; i<N; i++){
            if(this.isFull(i,0)){
                return true;
            }
        }
        return false;
    }

    // Get N;
    public int getN() {
        return N;
    }
}
